#ifndef __I2C_Driver_H
#define __I2C_Driver_H

#include <cstdio>

#ifdef ARDUINO

#include <Arduino.h>
#include <Rtos.h>

#endif

#include "nStream.h"

#define BUFFER_LENGTH 32
typedef unsigned char u8;
typedef unsigned short u16;
#ifndef ARDUINO
typedef struct {
    u8 port;
    uint32_t pin;
} port_t;
#endif

class I2C_Driver : public nStream {
public:
#ifdef ARDUINO

    I2C_Driver(PinName sda, PinName scl) {
        this->scl = scl;
        this->sda = sda;
    }

    void begin(uint32_t sda, uint32_t scl) {
        this->scl = digitalPinToPinName(scl);
        this->sda = digitalPinToPinName(sda);
        begin();
    }

#else

    I2C_Driver(port_t scl, port_t sda) {
        this->scl = scl;
        this->sda = sda;
    }

#endif

    void begin(uint8_t address, bool generalCall = false);

    void begin(bool generalCall = false);

    void begin(int address, bool generalCall = false) {
        begin((uint8_t) address, generalCall);
    }

    void start();

    int available() override;

    int read() override;

    int peek(void) override;

    void flush() override;

    int write(u8) override;

    int write(const u8 *, int) override;

    u8 endTransmission(u8 sendStop) override;

    u8 endTransmission() override {
        return endTransmission((u8) true);
    }

    void beginTransmission(uint8_t) override;

    uint8_t requestFrom(uint8_t address, uint8_t quantity, uint32_t iaddress, uint8_t isize, uint8_t sendStop);


    uint8_t requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop) {
        return requestFrom((uint8_t) address, (uint8_t) quantity, (uint32_t) 0, (uint8_t) 0, (uint8_t) sendStop);
    }

    uint8_t requestFrom(uint8_t address, int quantity, bool sendStop) {
        return requestFrom((uint8_t) address, (uint8_t) quantity, (uint8_t) sendStop);
    }

    uint8_t requestFrom(uint8_t address, uint8_t quantity) {
        return requestFrom((uint8_t) address, (uint8_t) quantity, (uint8_t) true);
    }

    uint8_t requestFrom(int address, int quantity) override {
        return requestFrom((uint8_t) address, (uint8_t) quantity, (uint8_t) true);
    }

    uint8_t requestFrom(int address, int quantity, int sendStop) override {
        return requestFrom((uint8_t) address, (uint8_t) quantity, (uint8_t) sendStop);
    }

    void allocateRxBuffer(int length);

    void allocateTxBuffer(int length);

    /**
      * @brief  Reset Rx/Tx buffer content to 0
      */
    void resetRxBuffer(void) {
        if (rxBuffer != nullptr) {
            memset(rxBuffer, 0, rxBufferAllocated);
        }
    }

    void resetTxBuffer(void) {
        if (txBuffer != nullptr) {
            memset(txBuffer, 0, txBufferAllocated);
        }
    }

    int available_wait(int Delay) override;

    int read_wait(int timeout = -1) override;

private:
#ifdef ARDUINO
    PinName scl, sda;
#else
    port_t scl{}, sda = {};
#endif
    uint8_t *rxBuffer;
    uint16_t rxBufferAllocated;
    uint16_t rxBufferIndex;
    uint16_t rxBufferLength;

    uint8_t txAddress;
    uint8_t *txBuffer;
    uint16_t txBufferAllocated;
    uint16_t txDataSize;

    uint8_t transmitting;

    uint8_t ownAddress;

    int I2C_SendByte(u8 SendByte);

    bool I2C_WriteByte(u8 SendByte, u8 WriteAddress);

    bool I2C_BufferWrite(u8 *pBuffer, u8 length, u8 WriteAddress);

    void I2C_PageWrite(u8 *pBuffer, u8 length, u16 WriteAddress, u8 DeviceAddress);

    bool I2C_ReadByte(u8 *pBuffer, u8 length, u8 ReadAddress);

    bool I2C_Start();

    void I2C_Stop();

    void I2C_Ack();

    void I2C_NoAck();

    bool I2C_WaitAck();

    u8 I2C_ReceiveByte();

    void SDA_H();

    void SDA_L();

    void SCL_H();

    void SCL_L();

    int SDA_read();

    int txBufferLength;
    int txBufferIndex;
};

#endif
